iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
Modern Web

從Vue學React!不只要會用,還要真的懂~系列 第 28

【Day 28】 從vue-router學react-router(上) - 基本設定及一些常見用法

  • 分享至 

  • xImage
  •  

今天主要一樣會偏向於從使用的方式來了解React相關的路由使用方式,不過在這之前還是會先來認識提到React的路由就會看到的兩個名字,分別是react-router和react-router-dom,那廢話不多說了!直接開始我們今天的主題吧!接下來Router學習的部分,一樣會以react-router為主,但是會以從Vue在使用vue-router時,常用的一些用法和使用情境的對照,來了解相同的情境下,在React中可能可以怎麼使用。

使用前,先認識他們!react-router & react-router-dom

對Vue熟悉的朋友,應該對於vue-router都很熟悉,如果會需要使用到路由就一定會使用vue-router。不過在React的世界對於這部分可能有一點點不太一樣,如果下意識透過相同邏輯的命名去查詢react-router的時候,可能會發現不只有react-router,還有react-router-dom,所以React的Route到底是要安裝哪一套呢?這裡就來認識一下react-router和react-router-dom吧!

react-router是什麼?

react-router是react在處理路由的核心Library,也就是說換頁的功能主要會由react-router來實作,它可以被使用於web和native等不同環境上。

react-router-dom是什麼?

react-router-dom是基於react-router的Library,是react-router擴充適用於web環境的一些用法的Libray,例如:Link。由於是擴充來符合web使用情境的Library,所以react-router-dom只能使用於web環境上。也就是說如果是使用react native進行APP的開發,就無法使用react-router-dom。也因為react-router-dom是基於react-router的Library,所以只要安裝react-router-dom,就會包含react-router的部分,不需要額外安裝react-router

盤點vue-router的常見用法

在正式進入react-router-dom的使用之前,先來盤點一些使用vue-router時,經常會使用到的用法,接下來就會由這幾個功能下去看使用react-router-dom時,可以怎麼做。

  • 路由定義及嵌套路由
  • 處理匹配不到的route頁面以及重新導向
  • 路由相關的Lazy Loading設定
  • 頁面中的超連結
  • 判斷超連結是否有匹配到路由
  • 路由的go, back, push操作
  • 取得路由資料、參數
  • 路由守衛

安裝react-router-dom

首先,從基本安裝的步驟正式進入學習react-router-dom的環節吧!
可以透過以下指令把react-router-dom安裝到自己的專案中。

# NPM
npm install react-router-dom

# Yarn
yarn add react-router-dom

安裝好react-router-dom後,也會把react-router也安裝好,接下來就直接來看一些常見的使用情境囉!

路由定義及嵌套路由、動態路由

使用react-router-dom和使用vue-router一樣,都需要設定特定路由對應到的頁面,不過vue-router在這部分的設定上,會是以一個物件下設定相關的內容,而react-router-dom則是在v6之後,才能用類似vue-router的用物件來設定路由,在v5之前都只能用Router、Switch搭配Route的方式把詳細的route內容定義Jsx裡面。

定義route

vue-router的route定義方式

vue-router是用一個陣列搭配物件來定義路由,所以很好管裡,也可以和template上的內容分開,不需要混在一起寫。

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'MainPage',
      component: MainPage
    },
    {
      path: '/page-a',
      name: 'PageA',
      component: PageA
    },
    {
      path: '/page-b',
      name: 'PageB',
      component: PageB
    },
  ]
})

react-router-dom的route定義方式

v5的方式:

function App() {
  return (
    <div className="App">
      <Router>
        <Switch>
          <Route exact path="/" element={<MainPage />}></Route>
          <Route path="page-a" element={<PageA />}></Route>
          <Route path="page-b" element={<PageB />}></Route>
        </Switch>
      </Router>
    </div>
  );
}

定義完路由後,還要回到入口檔案裡將HashRouter或HisotryRouter包在App外面,定義的路由才可以被套用。

import { HashRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    // 在APP外用HashRouter包起來
    <HashRouter>
       <App />
    </HashRouter>
  </React.StrictMode>
);

v6的方式
如果是將路由定義在jsx裡面,v6版本之後,改成用Routes和Route搭配。

import MainPage from './MainPage';
import PageA from './PageA';
import PageB from './PageB';

function App() {
  return (
    <div className="App">
      <Routes>
        <Route path="/" element={<MainPage />}></Route>
        <Route path="page-a" element={<PageA />}></Route>
        <Route path="page-b" element={<PageB />}></Route>
      </Routes>
    </div>
  );
}

除了上述的定義方法外,還可以用createHashRouter或是createHistoryRouter的寫法,透過傳入一個陣列來定義路由。這個方法也就是跟vue-router比較相似的方式,也是我自己個人認為比較好管理的路由設定方式。

使用createHashRouter,搭配陣列物件的寫法,定義路由。

import { createHashRouter } from 'react-router-dom';
import MainPage from '../MainPage';
import PageA from '../PageA';
import PageB from '../PageB';

const Router = createHashRouter([
  {
    path: '/',
    element: <MainPage />
  },
  {
    path: 'page-a',
    element: <PageA />
  },
  {
    path: 'page-b',
    element: <PageB />
  },
]);

export default Router;

一樣需要把設定好的router帶到App裡面,這裡使用RouterProvider把設定好的Router帶上。

import { RouterProvider } from 'react-router-dom';
import Router from './router';

function App() {
  return (
    <div className="App">
      <RouterProvider router={Router} />
    </div>
  );
}

目前這樣的寫法,root.render的地方不需要加上HashRouter。

root.render(
  <React.StrictMode>
    <App /> 
  </React.StrictMode>
);

設定嵌套路由

如果想要調整為嵌套路由,原本就使用物件進行設定的vue-router的設定方式是比較簡單易懂的,而react-router-dom的寫法則是依照是把路由設定在jsx裡面的方法,還是用陣列來設定的方式而有不同。

vue-router

可以透過children屬性來定義嵌套的路由。

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    // 略...
    {
      // 定義嵌套的route,並且定義嵌套route的Layout
      path: '/parent',
      component: Layout,
      children: [
        {
          path: '',
          name: 'MainChild',
          component: MainChild,
        },
        // 略...
      ]
    },
  ]
})

被當為嵌套路由的父元件中,則需要加上RouterView,來顯示子路由的內容。

<template>
  <div>
    <RouterView />
  </div>
</template>

react-router-dom

將route的設定內容直接寫在jsx的寫法。

function App() {
  return (
    <div className="App">
      <Routes>
        <Route path="/" element={<MainPage />}></Route>
        <Route path="page-a" element={<PageA />}></Route>
        <Route path="page-b" element={<PageB />}></Route>
        // 定義嵌套的route,並且定義嵌套route的Layout
        <Route path="parent" element={<Layout />}>
          <Route index element={<MainChild />} />
          <Route path="child-a" element={<ChildA />} />
          <Route path="child-b" element={<ChildB />} />
        </Route>
      </Routes>
    </div>
  );
}

嵌套route用物件定義的寫法跟vue-router的寫法很相似,一樣是用children屬性來設定嵌套路由。

const router = createHashRouter([
  // 略...
  {
    // 定義嵌套的route,並且定義嵌套route的Layout
    path: 'parent',
    element: <Layout />,
    children: [
      {
        path: '',
        element: <MainChild />
      },
      // 略...
    ]
  },
]);

跟vue-router一樣,被當為嵌套路由的父元件中,也需要加上一些東西才能把子路由顯示出來,react-router-dom是使用Outlet,才可以在父元件中顯示子路由的內容。

import { Outlet, NavLink } from 'react-router-dom';

export default function Layout() {
  return (
    <div>
      <Outlet />
    </div>
  )
}

最後呈現的效果也就會是這樣。
https://i.imgur.com/fwzpUr6.gif

處理匹配不到的路由頁面以及重新導向

除了定義一般的路由外,也會需要處理使用者打了一些我們未定義好的網址,或是遇到某些網址需要被導向其他頁面的狀況,這時候需要再定義好的路由中,做一些其他的設定。

處理匹配不到的路由

vue-router
在路由設定中加上動態路由搭配正規表達式的用法,讓vue-router去做匹配檢查

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    // 略...
    {
      // :pathMatch是動態路由的部分,後面則是正規表達式的部分
      path: '/:pathMatch(.*)*',
      name: 'NotFoundPage',
      component: NotFoundPage,
    },
  ]
})

react-router-dom
react-router-dom的話,可以把path設定為*,當沒有匹配到patch,就會顯示NotFoundPage

<Routes>
   // 略...
  <Route path="*" element={<NotFoundPage />} />
</Routes>

用物件設定的寫法一樣是設定一個path為*的route。

const router = createHashRouter([
  // 略 ...
  {
    path: '*',
    element: <NotFoundPage />    
  }
]);

重新導向

vue-router
如果要設定重新定向,可以使用redirect。如果延續前面404頁面的設定的話,可以調整成這樣。

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    // 略...
    {
      path: '/404',
      name: 'NotFoundPage',
      component: NotFoundPage,
    },
    {
      path: '/:pathMatch(.*)*',
      redirect: '/404',
    },
  ]
})

react-router-dom
react-router-dom的重新導向是使用Navigate,延續前面404頁面的設定的話,可以調整成這樣。

<Routes>
  // 略...
  <Route path="404" element={<NotFoundPage />} />
  <Route path="*" element={<Navigate to="/404" replace />} />
</Routes>

使用物件設定,一樣也是用Navigate下去重新定向。

const router = createHashRouter([
  // 略...
  {
    path: '404',
    element: <NotFoundPage />    
  },
  {
    path: '*',
    element: <Navigate to="/404" />   
  }
]);

動態路由的設定

在動態路由設定的部分,vue-router和react-router-dom都是透過「:」寫在路由的設定中。
例如以下這樣,就可以當id是動態的狀況下,還是會被導到對應的頁面。

path: ':id',

路由相關的Lazy Loading設定

這部分跟之前提到過的lazy有關的部分,也就是讓路由對應到的元件可以用動態加載的方式載入。在看要怎麼做這部分的設定之前,可以先觀察我們原先的寫法,在前面例子的寫法,都沒有用到lazy loading的設定,所以不論是Vue專案或是React專案,在Dev Tools觀察的時候,都可以發現到剛進頁面,不管這個元件有沒有被使用在當前的畫面上,都有被載入。

如果是Vue的話,可以看到有一堆還沒使用到的Vue檔案。
https://ithelp.ithome.com.tw/upload/images/20231004/20130914zxPcLCzujh.png

如果是React的話,則可以看到有沒有用到的元件出現在bundle.js檔案裡面。
https://ithelp.ithome.com.tw/upload/images/20231004/201309142i0M91LYUj.png

vue-router
vue-router是透過箭頭函式來讓路由對應的元件以lazy loading的方式加載。

import { createRouter, createWebHashHistory } from 'vue-router'
import MainPage from '../views/MainPage.vue'

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'MainPage',
      component: MainPage
    },
    {
      path: '/page-a',
      name: 'PageA',
      component: () => import('../views/PageA.vue')
    },
    {
      path: '/page-b',
      name: 'PageB',
      component: () => import('../views/PageB.vue')
    },
    // 略...  
  ]
})

調整成箭頭函式之後,只有在使用到該元件時,才會載入對應的元件。
https://i.imgur.com/f2y8aar.gif

react-router-dom
react-router-dom則是用lazy搭配Suspense來讓路由元件以lazy loading的方式加載。

先來看Routes的寫法,主要是使用lazy來import我們要放在route中的元件。

import MainPage from './MainPage';
import { lazy } from 'react';
const PageA = lazy(() => import('./PageA'));

function App() {
  return (
    <div className="App">
      <Routes>
        <Route exact path="/" element={<MainPage />}></Route>
        <Route path="page-a" element={<PageA />}></Route>
      </Routes>
    </div>
  );
}

再到root的地方用Suspense把App包起來。

root.render(
  <React.StrictMode>
    <HashRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <App />
      </Suspense>
    </HashRouter>
  </React.StrictMode>
);

使用物件定義路由的方式,也是一樣使用lazy搭配Suspense。

import { createHashRouter } from "react-router-dom";
import { lazy, Suspense } from 'react';

const PageA = lazy(() => import('../PageA'));

const Router = createHashRouter([
  {
    path: '/',
    element: <MainPage />
  },
  {
    path: 'page-a',
    element: (
      <Suspense>
        <PageA />
      </Suspense>
    )
  },
  // 略...
]);

這樣調整過後,一樣也是只有使用到對應的頁面,才會把相對應的元件載入。
https://i.imgur.com/qG0thZv.gif

今天認識了react-dom以及react-router-dom,也從幾個平常我們在使用vue-router的時候,會用到的一些router基本用法來學習react-router-dom的使用。今天已經有先盤點出一些vue-router的常見用法,但還沒把這個清單中的項目,都用react-router-dom再看過一遍,明天會繼續來看剩下幾個常見的用法。


上一篇
【Day 27】 SPA與他的小夥伴 - 路由(Route)
下一篇
【Day 29】 從vue-router學react-router(下) - 更多的常見用法
系列文
從Vue學React!不只要會用,還要真的懂~30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言